#include "veml7700.h"
#include "stdio.h"

  uint16_t register_cache[4];

uint8_t sendData(int fd,uint8_t command, uint16_t data)
{
  if (wiringPiI2CWriteReg16(fd, command, data) == -1){
    return STATUS_ERROR;
  }
  return STATUS_OK;
}

static uint8_t receiveData(int fd,uint8_t command, uint16_t *data)
{
  if ((*data = wiringPiI2CReadReg16(fd, command)) == -1){
    return STATUS_ERROR;
  }
  return STATUS_OK;
}

uint8_t setGain(int fd,enum als_gain_t gain)
{
  uint16_t reg = ((register_cache[COMMAND_ALS_SM] & ~ALS_SM_MASK) | 
                   (((uint16_t)(gain) << ALS_SM_SHIFT) & ALS_SM_MASK) );
  register_cache[COMMAND_ALS_SM] = reg;
  return sendData(fd,COMMAND_ALS_SM, reg);
}

uint8_t getGain(int fd,enum als_gain_t  *gain)
{
   *gain = (enum als_gain_t)(
    (register_cache[COMMAND_ALS_SM] & ALS_SM_MASK) >> ALS_SM_SHIFT );
  return STATUS_OK;
}

uint8_t setIntegrationTime(int fd,enum als_itime_t itime)
{
  uint16_t reg = ( (register_cache[COMMAND_ALS_IT] & ~ALS_IT_MASK) | 
                   (((uint16_t)(itime) << ALS_IT_SHIFT) & ALS_IT_MASK) );
  register_cache[COMMAND_ALS_IT] = reg;
  return sendData(fd,COMMAND_ALS_IT, reg);
}

uint8_t getIntegrationTime(int fd, enum als_itime_t *itime)
{
  *itime = (enum als_itime_t)(
    (register_cache[COMMAND_ALS_IT] & ALS_IT_MASK) >> ALS_IT_SHIFT );
  return STATUS_OK;
}

uint8_t setPersistence(int fd ,enum als_persist_t persist)
{
  uint16_t reg = ( (register_cache[COMMAND_ALS_PERS] & ~ALS_PERS_MASK) | 
                   (((uint16_t)(persist) << ALS_PERS_SHIFT) & ALS_PERS_MASK) );
  register_cache[COMMAND_ALS_PERS] = reg;
  return sendData(fd,COMMAND_ALS_PERS, reg);
}

uint8_t setPowerSavingMode(int fd,enum als_powmode_t powmode)
{
  uint16_t reg = ( (register_cache[COMMAND_PSM] & ~PSM_MASK) | 
                   (((uint16_t)(powmode) << PSM_SHIFT) & PSM_MASK) );
  register_cache[COMMAND_PSM] = reg;
  return sendData(fd,COMMAND_PSM, reg);
}

uint8_t setPowerSaving(int fd,uint8_t enabled)
{
  uint16_t reg = ( (register_cache[COMMAND_PSM_EN] & ~PSM_EN_MASK) | 
                   (((uint16_t)(enabled) << PSM_EN_SHIFT) & PSM_EN_MASK) );
  register_cache[COMMAND_PSM_EN] = reg;
  return sendData(fd,COMMAND_PSM_EN, reg);
}

uint8_t setInterrupts(int fd, uint8_t enabled)
{
  uint16_t reg = ( (register_cache[COMMAND_ALS_INT_EN] & ~ALS_INT_EN_MASK) | 
                   (((uint16_t)(enabled) << ALS_INT_EN_SHIFT) & 
                    ALS_INT_EN_MASK) );
  register_cache[COMMAND_ALS_INT_EN] = reg;
  return sendData(fd,COMMAND_ALS_INT_EN, reg);
}

uint8_t setPower(int fd,uint8_t on)
{
  uint16_t reg = ( (register_cache[COMMAND_ALS_SD] & ~ALS_SD_MASK) | 
                   (((uint16_t)(~on) << ALS_SD_SHIFT) & ALS_SD_MASK) );
  register_cache[COMMAND_ALS_SD] = reg;
  uint8_t status = sendData(fd,COMMAND_ALS_SD, reg);
  if (on) {
    delay(3); // minimu 2.5us delay per datasheet
  }
  return status;
}


uint8_t setALSHighThreshold(int fd,uint16_t thresh)
{
  return sendData(fd,COMMAND_ALS_WH, thresh);
}

uint8_t setALSLowThreshold(int fd,uint16_t thresh)
{
  return sendData(fd,COMMAND_ALS_WL, thresh);
}

uint8_t getALS(int fd,uint16_t *als)
{
  return receiveData(fd,COMMAND_ALS, als);
}

uint8_t getWhite(int fd,uint16_t *white)
{
  return receiveData(fd,COMMAND_WHITE, white);
}

uint8_t getLowThresholdEvent(int fd,uint8_t *event)
{
  uint16_t reg;
  uint8_t status = receiveData(fd,COMMAND_ALS_IF_L, &reg);
  *event = (reg & ALS_IF_L_MASK) >> ALS_IF_L_SHIFT;
  return status;
}

void scaleLux2(int fd,uint16_t raw_counts, float *lux)
{
  enum als_gain_t gain;
  enum als_itime_t itime;
  getGain(fd,&gain);
  getIntegrationTime(fd,&itime);

  float factor1, factor2;

  switch(gain & 0x3){
  case ALS_GAIN_x1:
    factor1 = 1.f;
    break;
  case ALS_GAIN_x2:
    factor1 = 0.5f;
    break;
  case ALS_GAIN_d8:
    factor1 = 8.f;
    break;
  case ALS_GAIN_d4:
    factor1 = 4.f;
    break;
  default:
    factor1 = 1.f;
    break;
  }

  switch(itime){
  case ALS_INTEGRATION_25ms:
    factor2 = 0.2304f;
    break;
  case ALS_INTEGRATION_50ms:
    factor2 = 0.1152f;
    break;
  case ALS_INTEGRATION_100ms:
    factor2 = 0.0576f;
    break;
  case ALS_INTEGRATION_200ms:
    factor2 = 0.0288f;
    break;
  case ALS_INTEGRATION_400ms:
    factor2 = 0.0144f;
    break;
  case ALS_INTEGRATION_800ms:
    factor2 = 0.0072f;
    break;
  default:
    factor2 = 0.2304f;
    break;
  }

  *lux = raw_counts * factor1 * factor2;

  printf("this is value %f\n",lux);

  // apply correction from App. Note for all readings
  //   using Horner's method
  *lux = *lux * (1.0023f + *lux * (8.1488e-5f + *lux * (-9.3924e-9f + 
                                                    *lux * 6.0135e-13f)));
}


void scaleLux(int fd,uint16_t raw_counts, float *lux)
{
  enum als_gain_t gain;
  enum als_itime_t itime;
  getGain(fd,&gain);
  getIntegrationTime(fd,&itime);

  float factor1, factor2;

  switch(gain & 0x3){
  case ALS_GAIN_x1:
    factor1 = 1.f;
    break;
  case ALS_GAIN_x2:
    factor1 = 0.5f;
    break;
  case ALS_GAIN_d8:
    factor1 = 8.f;
    break;
  case ALS_GAIN_d4:
    factor1 = 4.f;
    break;
  default:
    factor1 = 1.f;
    break;
  }

  switch(itime){
  case ALS_INTEGRATION_25ms:
    factor2 = 0.2304f;
    break;
  case ALS_INTEGRATION_50ms:
    factor2 = 0.1152f;
    break;
  case ALS_INTEGRATION_100ms:
    factor2 = 0.0576f;
    break;
  case ALS_INTEGRATION_200ms:
    factor2 = 0.0288f;
    break;
  case ALS_INTEGRATION_400ms:
    factor2 = 0.0144f;
    break;
  case ALS_INTEGRATION_800ms:
    factor2 = 0.0072f;
    break;
  default:
    factor2 = 0.2304f;
    break;
  }

  *lux = raw_counts * factor1 * factor2;

  printf("this is value %f\n",*lux);


  // apply correction from App. Note for all readings
  //   using Horner's method
  *lux = *lux * (1.0023f + *lux * (8.1488e-5f + *lux * (-9.3924e-9f + 
                                                    *lux * 6.0135e-13f)));
}


uint8_t getALSLux(int fd,float *lux)
{
  uint16_t raw_counts;
  uint8_t status = getALS(fd,&raw_counts);
  scaleLux(fd,raw_counts, lux);
  return status;
}

uint8_t getWhiteLux(int fd,float *lux)
{
  uint16_t raw_counts;
  uint8_t status = getWhite(fd,&raw_counts);
  scaleLux(fd,raw_counts, lux);
  return status;
}



uint8_t sampleDelay(int fd)
{
  enum als_itime_t itime;
  getIntegrationTime(fd,&itime);

  // extend nominal delay to ensure new sample is generated
  #define extended_delay(ms) delay(2*(ms))

  switch(itime){
  case ALS_INTEGRATION_25ms:
    extended_delay(25);
    break;
  case ALS_INTEGRATION_50ms:
    extended_delay(50);
    break;
  case ALS_INTEGRATION_100ms:
    extended_delay(100);
    break;
  case ALS_INTEGRATION_200ms:
    extended_delay(200);
    break;
  case ALS_INTEGRATION_400ms:
    extended_delay(400);
    break;
  case ALS_INTEGRATION_800ms:
    extended_delay(800);
    break;
  default:
    extended_delay(100);
    break;
  }
}